共计 3176 个字符,预计需要花费 8 分钟才能阅读完成。
引入
1. 为什么要使用深浅拷贝
- 涉及到容器类型的修改操作时, 想要保留原来的数据或修改后的数据, 这是就需要使用到深浅拷贝来进行操作了
2. 以下使用列表 list1 = ["str", 123, [111,222]]
拷贝来进行实验
一. 赋值操作
1. 赋值操作原理
- 赋值过后, 源列表与新列表指向的是同一个内存地址
list2 = list1
2. 示例
👜创建一个列表, 包含字符串, 整形和列表(可变), 并将其赋值给另一个变量名
list1 = ["str", 123, [111,222]]
list2 = list1
👜查看两个列表的 "id", 可以发现 "id" 不变, 是同一个
print(id(list1)) #2248006456392
print(id(list2)) #2248006456392
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2248006252720 140725163491392 2248006455880
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2248006252720 140725163491392 2248006455880
👜当改变 "list1" 内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2248007720496
print(list2[0],id(list2[0]))
# aaa 2248007720496
👜发现 "list1" 改变的值的内存地址已经变化了, 说明是产生的新值, 与原来的 "str" 取消了对应
👜而 "list2" 中的值和 "id" 也跟着改变
👜当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2248006455880
print(list2[2],id(list2[2]))
# [333, 222] 2248006455880
👜我们发现两个列表的值都发生了改变, 但子列表的 "id" 还都是不变的
3. 总结
- 赋值操作列表与新列表都是指向同一内存地址,2 个列表中,只要有一个人的列表中的索引所对应的值的内存地址改变,则都改变
- 也就是把源列表容器的内存地址完完整整的多绑定一份交给新列表
二. 浅拷贝
1. 浅拷贝原理
- 把源列表第一层的内存地址 不加区分 (不区分是可变还是不可变类型) 的完全 copy 给一份新列表
- 对源列表 copy 之后, 产生的新列表的内存地址发生了改变, 它们不再是同一个列表
- 但是新列表与源列表中的可变和不可变类型的值在修改之前都是指向同一个值
list2 = list1.copy()
2. 示例
👑创建一个列表, 包含字符串, 整形和列表(可变), 并将其浅拷贝
list1 = ["str", 123, [111,222]]
list2 = list1.copy()
👑查看两个列表的 "id", 可见是产生了新的一个内存地址
print(id(list1)) #2243511014472
print(id(list2)) #2243541368584
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2243510810800 140725163491392 2243511013960
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2243510810800 140725163491392 2243511013960
👑复制过来的元素内存地址都是同一个, 说明复制的只是变量名与内存地址的对应关系, 指向的是同一个内存地址
👑当改变 "list1" 内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2240322768432
print(list2[0],id(list2[0]))
# str 2243510810800
👑发现 "list1" 改变的值的内存地址已经变化了, 说明是产生的新值, 与原来的 "str" 取消了对应
👑而 "list2" 中的值和 "id" 都没变, 说明还是原来的对应关系
👑当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2243511013960
print(list2[2],id(list2[2]))
# [333, 222] 2243511013960
👑我们发现两个列表的值都发生了改变, 但子列表的 "id" 还都是不变的
3. 浅拷贝方法总结
list1 = ["str", 123, [111,222]]
👑内置方法
list2 = list1.copy()
👑完全切片
list3 = list[:]
👑模块
import copy
list4 = copy.copy(list1)
4. 总结
- 对源列表中不可变类型的值进行修改以后,对于不可变类型的值,都是产生新值,让源列表的索引指向新的内存地址,并不会影响新列表
- 对源列表中可变类型的值进行修改以后,对于可变类型,我们可以改变类型中包含的值,但这个可变容器本身内存地址不变
- 即新列表的索引仍然指向原来的内存地址,于是新列表也跟着受影响。
三. 深拷贝
1. 深拷贝原理
- 深拷贝对容器类型中的每一层得数据 加以区分 (对可变不可变类型区分对待)
- 针对不可变类型, 拷贝以后任然还是用原来值的内存地址, 但值一发生改变就会产生新值, 于是与原来的值没有任何关联
- 针对可变类型, 拷贝之后会申请新的内存地址存放, 在新的内存地址中 再次区分(可变还是不可变)
- 再次区分判断后, 于是重复针对不可变类型和针对可变类型所进行的操作, 以此类推
import copy
,copy.deepcopy(list)
2. 示例
🐷导入模块
import copy
🐷创建一个列表, 包含字符串, 整形和列表(可变), 并将其深拷贝
list1 = ["str", 123, [111,222]]
list2 = copy.deepcopy(list1)
🐷查看两个列表的 "id", 可见是产生了新的一个内存地址
print(id(list1)) #2655395993736
print(id(list2)) #2655397313096
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2655393364144 140725163491392 2655397312904
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2655393364144 140725163491392 2655397313672
🐷针对不可变类型, 拷贝以后任然还是用原来值的内存地址
🐷针对可变类型, 拷贝之后会申请新的内存地址存放
🐷当改变 "list1" 内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2655394824496
print(list2[0],id(list2[0]))
# str 2655393364144
🐷发现 "list1" 改变的值的内存地址已经变化了, 说明是产生的新值, 与原来的 "str" 取消了对应
🐷而 "list2" 中的值和 "id" 都没变, 说明 ""list1" 的改变与 "list2" 无关
🐷当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2655397312904
print(list2[2],id(list2[2]))
# [111, 222] 2655397313672
🐷我们发现 "list1" 的变化依然与 "list2" 无关
3. 总结
- 深拷贝等于是把源列表和新列表完完整整的独立开来互不干扰
- 这样就可以保存原数据, 又可以保存操作后的数据
正文完